home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapdupspath.py < prev    next >
Text File  |  2004-01-05  |  39KB  |  1,002 lines

  1. # QuArK  -  Quake Army Knife
  2. #
  3. # Copyright (C) 1996-2000 Armin Rigo
  4. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  5. # FOUND IN FILE "COPYING.TXT"
  6. #
  7. #$Header: /cvsroot/quark/runtime/plugins/mapdupspath.py,v 1.47 2002/08/04 11:49:24 decker_dk Exp $
  8.  
  9.  
  10. Info = {
  11.    "plug-in":       "Path Duplicator",
  12.    "desc":          "Path Duplicator",
  13.    "date":          "3 feb 01",
  14.    "author":        "Decker, also tiglari",
  15.    "author e-mail": "decker@planetquake.com, tiglari@planetquake.com",
  16.    "quark":         "Version 6.2"
  17. }
  18.  
  19. import quarkx
  20. from quarkpy.maputils import *
  21. import plugins.deckerutils
  22. import quarkpy.mapduplicator
  23. import quarkpy.maphandles
  24. import quarkpy.mapentities
  25. import quarkpy.qhandles
  26. import quarkpy.mapbtns
  27. import quarkpy.dlgclasses
  28. import math
  29. StandardDuplicator = quarkpy.mapduplicator.StandardDuplicator
  30. DuplicatorManager = quarkpy.mapduplicator.DuplicatorManager
  31. DupOffsetHandle = quarkpy.mapduplicator.DupOffsetHandle
  32.  
  33. from quarkpy.mapentities import ObjectOrigin
  34. from quarkpy.maphandles import GetUserCenter
  35. from quarkpy.maphandles import UserCenterHandle
  36.  
  37.  
  38. #
  39. # destructive, do to copies
  40. #
  41. def evaluateDuplicators(group):
  42.     for obj in group.subitems:
  43.        if obj.type==":b" or obj.type==":g":
  44.            evaluateDuplicators(obj)
  45.     getmgr = quarkpy.mapduplicator.DupManager
  46.     for obj in group.subitems:
  47.        if obj.type==":d":
  48.             index=group.subitems.index(obj)
  49.             mgr = getmgr(obj)
  50.             image = 0
  51.             while 1:
  52.                 objlist = mgr.buildimages(image)
  53.                 if len(objlist)==0:
  54.                     break
  55.                 image = image + 1
  56.                 new = quarkx.newobj("%s (%d):g" % (obj.shortname, image))
  57.                 for o in objlist:
  58.                     new.appenditem(o)
  59.                 group.removeitem(index)
  60.                 group.insertitem(index, new)
  61.  
  62. #
  63. #  Two ways of making axes that don't `twist'.  This leads to the
  64. #    section not joining properly, but maybe something can be done
  65. #    about this someday (with curved `elbows', for example)
  66. #
  67. def MakeAxes2(x):
  68.     xn=x.normalized
  69.     if abs(xn*quarkx.vect(0,1,0))<.1:
  70.         z=(xn^quarkx.vect(-1,0,0)).normalized
  71.     else:
  72.         z=(xn^quarkx.vect(0,1,0)).normalized
  73.     y = (z^x).normalized
  74.     return xn, y, z
  75.  
  76. def Vertical(x):
  77.     x=x.normalized
  78.     return abs(x*quarkx.vect(0,0,1))>.99999
  79.  
  80. def MakeLevelAxes(x):
  81.     x=x.normalized
  82.     mapx, mapy, mapz = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
  83.     dot = x*mapz
  84.     #
  85.     # project onto the xy plane
  86.     #
  87.     xp = quarkx.vect(x*mapx,x*mapy,0).normalized
  88.     y = matrix_rot_z(math.pi/2)*xp
  89.     return x, y, (x^y).normalized
  90.  
  91. def NewAxes(prevaxes, newx):
  92.     try:
  93.         mat=matrix_rot_u2v(prevaxes[0],newx)
  94.         return newx, mat*prevaxes[1], mat*prevaxes[2]
  95.     except:  # no angle
  96.         return prevaxes
  97.  
  98. def MakeUniqueTargetname():
  99.     import time
  100.     return "t" + time.strftime("%Y%m%d%H%M%S", time.gmtime(time.time()))
  101.  
  102.  
  103. SMALL=0.001
  104.  
  105. def getNormalFaces(faces, axis):
  106.     def normalFace(face,axis=axis):
  107.         return abs(face.normal*axis-1)<SMALL
  108.     return filter(normalFace,faces)
  109.  
  110. def getends(group,x_axis):
  111.     list = group.findallsubitems("",":f")
  112.     return getNormalFaces(list,-x_axis), getNormalFaces(list,x_axis)
  113.  
  114. #
  115. # Position following path points
  116. #
  117. class PositionFollowingDlg (quarkpy.dlgclasses.LiveEditDlg):
  118.     #
  119.     # dialog layout
  120.     #
  121.  
  122.     endcolor = AQUA
  123.     size = (210,250)
  124.     dfsep = 0.50
  125.  
  126.     dlgdef = """
  127.         {
  128.         Style = "9"
  129.         Caption = "Position Following"
  130.  
  131.         sep: = {Typ="S" Txt=" "}
  132.  
  133.         number: =
  134.         {
  135.         Txt = "Number"
  136.         Typ = "EF1"
  137.         Hint = "How many of the following path points to position" $0D " (new ones will be made if needed)"
  138.         }
  139.  
  140.         sep: = {Typ="S" Txt=" "}
  141.  
  142.         angles: =
  143.         {
  144.         Txt = "First Pitch Yaw"
  145.         Typ = "EQ"
  146.         Hint = "Pitch Yaw angles to next, in degrees, map space"
  147.         }
  148.  
  149.         sep: = {Typ="S" Txt=" "}
  150.  
  151.         more_angles: =
  152.         {
  153.         Txt = "More Pitch Yaw"
  154.         Typ = "EQ"
  155.         Hint = "Pitch Yaw angles for remaining, in degrees, relative to previous"
  156.         }
  157.  
  158.         sep: = {Typ="S" Txt=" "}
  159.  
  160.         distance: =
  161.          {
  162.          Txt = "Distance"
  163.          Typ = "EU"
  164.          Hint = "Distance to next, in units"
  165.          }
  166.  
  167.  
  168.          sep: = {Typ="S" Txt=" "}
  169.  
  170.         shifttail: =
  171.          {
  172.          Txt = "Shift Tail"
  173.          Typ = "X"
  174.          Hint = "If checked, remaining points are moved to retain distance w.r.t last in moved series"
  175.          }
  176.  
  177.          sep: = {Typ="S" Txt=" "}
  178.  
  179.          exit:py = { Txt=""}
  180.     }
  181.     """
  182.  
  183.  
  184. class PathDuplicatorPointHandle(quarkpy.qhandles.IconHandle):
  185.  
  186.     def __init__(self, origin, centerof, pathdupmaster=0):
  187.         quarkpy.qhandles.IconHandle.__init__(self, origin, centerof)
  188.         self.pathdupmaster = pathdupmaster
  189.         self.mainpathdup=self.centerof.parent.findname("Path Duplicator:d")
  190.  
  191.  
  192.     def findpathdupcornerwith(self, list, entitykey, entitykeydata):
  193.         for e in list:
  194.             if e.type == ':d':
  195.                 if e[entitykey] == entitykeydata:
  196.                     return e
  197.         return None
  198.  
  199.     def makecopy(self, original):
  200.         new = original.copy()
  201.         # Erase all subitems from object.
  202.         while (new.itemcount > 0):
  203.             new.removeitem(new.itemcount-1)
  204.         # Erase all key/keydata in object.
  205.         for key in original.dictspec.keys():
  206.             new[key] = ""
  207.         # Copy only those key/keydata's we're interested in.
  208.         new.shortname = "PathDup.Point"
  209.         new["macro"] = "dup path_point"
  210.         new["origin"] = original["origin"]
  211.         new["target"] = original["target"]
  212.         new["targetname"] = original["targetname"]
  213.         return new
  214.  
  215.     #
  216.     # For decent org., I wanted this to be a method of
  217.     #  PathPointDuplicator, but couldn't get it to
  218.     #  work.
  219.     #
  220.     def sourcelist2(self):
  221.         myself = self.centerof
  222.         list = []
  223.         if (myself.parent is not None):
  224.             for item in myself.parent.subitems:
  225.                 if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
  226.                     list.append(item)
  227.         return list
  228.  
  229.     def menu(self, editor, view):
  230.  
  231.         def after1click(m, self=self, editor=editor, view=view):
  232.             # Insert a copy of me, between me and whoever I point to, and give this
  233.             # new copy a unique targetname, which I should now point to instead.
  234.             # Try also to put the new copy, into the tree-view in targeting-order.
  235.             undo = quarkx.action()
  236.             new = self.makecopy(self.centerof)
  237.             new["targetname"] = MakeUniqueTargetname()
  238.             undo.setspec(self.centerof, "target", new["targetname"])
  239.             who_do_i_target = self.findpathdupcornerwith(self.centerof.parent.subitems, "targetname", new["target"])
  240.             if (who_do_i_target is not None):
  241.                 newtranslate = (who_do_i_target.origin - self.centerof.origin) / 2
  242.             else:
  243.                 prev_prev = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
  244.                 if (prev_prev is not None) and (prev_prev["macro"] == "dup path_point"):
  245.                     newtranslate = (self.centerof.origin - prev_prev.origin)
  246.                 else:
  247.                     newtranslate = quarkx.vect(64,0,0)
  248.             new.translate(newtranslate)
  249.             newtranslate = new.origin - quarkpy.qhandles.aligntogrid(new.origin, 1)
  250.             new.translate(newtranslate)
  251.             if ((who_do_i_target is not None) and (who_do_i_target.parent == self.centerof.parent)):
  252.                 undo.put(self.centerof.parent, new, who_do_i_target)
  253.             else:
  254.                 undo.put(self.centerof.parent, new)
  255.             editor.ok(undo, "Insert after PathDuplicator corner")
  256.             editor.layout.explorer.sellist = [new]
  257.  
  258.         def before1click(m, self=self, editor=editor, view=view):
  259.             # Insert a copy of me, between me and whoever points to ne, and give this
  260.             # new copy a unique targetname, which my previous should point to instead.
  261.             # Try also to put the new copy, into the tree-view in targeting-order.
  262.             undo = quarkx.action()
  263.             who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
  264.             new = self.makecopy(self.centerof)
  265.             new["targetname"] = MakeUniqueTargetname()
  266.             new["target"] = self.centerof["targetname"]
  267.             if (who_targets_me is not None) and (who_targets_me["macro"] == "dup path_point"):
  268.                 newtranslate = (who_targets_me.origin - self.centerof.origin) / 2
  269.             else:
  270.                 next_next = self.findpathdupcornerwith(self.centerof.parent.subitems, "targetname", self.centerof["target"])
  271.                 if (next_next is not None) and (next_next["macro"] == "dup path_point"):
  272.                     newtranslate = (self.centerof.origin - next_next.origin)
  273.                 else:
  274.                     newtranslate = quarkx.vect(-64,0,0)
  275.             new.translate(newtranslate)
  276.             newtranslate = new.origin - quarkpy.qhandles.aligntogrid(new.origin, 1)
  277.             new.translate(newtranslate)
  278.             undo.put(self.centerof.parent, new, self.centerof)
  279.             if (who_targets_me is not None):
  280.                 undo.setspec(who_targets_me, "target", new["targetname"])
  281.             editor.ok(undo, "Insert before PathDuplicator corner")
  282.             editor.layout.explorer.sellist = [new]
  283.  
  284.         def remove1click(m, self=self, editor=editor, view=view):
  285.             # Tell whoever points to me, that it should now point to my target instead, as I am going to be removed now.
  286.             undo = quarkx.action()
  287.             what_is_my_target = self.centerof["target"]
  288.             who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
  289.             if who_targets_me is not None:
  290.                 undo.setspec(who_targets_me, "target", what_is_my_target)
  291.             undo.exchange(self.centerof, None);
  292.             editor.ok(undo, "Remove PathDuplicator corner");
  293.  
  294.         def speeddraw1click(m, self=self, editor=editor, view=view):
  295.             #
  296.             walker = self.centerof
  297.             who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", walker["targetname"])
  298.             while (who_targets_me is not None) and (who_targets_me["target"] is not None):
  299.                 walker = who_targets_me
  300.                 who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", walker["targetname"])
  301.             if (walker["speeddraw"] is not None):
  302.                 if (walker["speeddraw"] == "1"):
  303.                     walker["speeddraw"] = "0"
  304.                 else:
  305.                     walker["speeddraw"] = "1"
  306.             #FIXME - How to redraw the duplicator, to reflect the change?!?
  307.  
  308.  
  309.         def selectdup1click(m, self=self, editor=editor):
  310.             editor.layout.explorer.uniquesel = self.mainpathdup
  311.             editor.invalidateviews()
  312.  
  313.         def selecttail1click(m, self=self, editor=editor):
  314.             center = self.centerof
  315.             list = self.sourcelist2()
  316.             pathlist = plugins.deckerutils.GetEntityChain(self.centerof["target"], list)
  317.             editor.layout.explorer.sellist = [center] + pathlist
  318.             editor.invalidateviews()
  319.  
  320.         def retarget1click(m, self=self, editor=editor):
  321.             group = self.centerof.parent
  322.             previous = self.centerof
  323.             i=1
  324.             undo = quarkx.action()
  325.             for item in group.subitems:
  326.                 if item["macro"]=='dup path_point':
  327.                     targetname = MakeUniqueTargetname()+'.'+`i`
  328.                     i=i+1
  329.                     undo.setspec(previous,"target",targetname)
  330.                     undo.setspec(item,"targetname",targetname)
  331.                     previous=item
  332.             undo.setspec(previous,"target","dpathX")
  333.             editor.ok(undo, 'retarget path corners')
  334.             editor.layout.explorer.uniquesel=self.centerof
  335.             editor.invalidateviews()
  336.  
  337.         def positionfollowing1click(m, self=self, editor=editor):
  338.             class pack:
  339.                   "stick stuff here"
  340.  
  341.             def setup(self,handle=self, pack=pack):
  342.                 list = handle.sourcelist2()
  343.                 if self.src["number"] is None:
  344.                     self.src["number"] = 1,
  345.                 pathlist = plugins.deckerutils.GetEntityChain(handle.centerof["target"], list)
  346.                 thisorigin = handle.centerof.origin
  347.                 if pathlist:
  348.                     nextorigin = pathlist[0].origin
  349.                     dist = nextorigin-thisorigin
  350.                     normdist = dist.normalized
  351.                     self.src["distance"] = "%.2f"%abs(dist)
  352.                     xax, yax, zax = (quarkx.vect(1,0,0),
  353.                                     quarkx.vect(0,1,0),
  354.                                     quarkx.vect(0,0,1))
  355.                     pitch = "%.1f"%(math.asin(normdist*zax)/deg2rad)
  356.                     yaw = "%.1f"%(math.atan2(normdist*yax, normdist*xax)/deg2rad)
  357.                     self.src["angles"] = pitch+' '+yaw
  358.                 pack.list=pathlist
  359.                 pack.thisorigin=thisorigin
  360.  
  361.             def action(self, handle=self, editor=editor, pack=pack):
  362.                 if self.src["distance"]:
  363.                     distance = eval(self.src["distance"])
  364.                 else:
  365.                     distance = 0
  366.                 pitch, yaw = read2vec(self.src["angles"])
  367.                 if not distance or pitch is None:
  368.                     return
  369.                 list = pack.list
  370.                 vangle = quarkx.vect(1,0,math.sin(pitch*deg2rad)).normalized
  371.                 normdist = matrix_rot_z(yaw*deg2rad)*vangle
  372.                 undo=quarkx.action()
  373.                 if list:
  374.                     next = list[0]
  375.                     new = next.copy()
  376.                 else:
  377.                     new = handle.centerof.copy()
  378.                     new["targetname"] = MakeUniqueTargetname()+'.0'
  379.                     undo.setspec(handle.centerof,"target",new["targetname"])
  380.                 shift = pack.thisorigin+distance*normdist-new.origin
  381.                 new.translate(shift)
  382.                 if list:
  383.                     undo.exchange(next, new)
  384.                 else:
  385.                     undo.put(handle.centerof.parent,new)
  386.                 number, = self.src["number"]
  387.                 pitch2, yaw2 = read2vec(self.src["more_angles"])
  388.                 for i in range(1, number):
  389.                     shift = None
  390.                     if pitch2 is None:
  391.                         continue
  392.                     pitch = pitch+pitch2
  393.                     yaw = yaw+yaw2
  394.                     vangle=quarkx.vect(1,0,math.sin(pitch*deg2rad)).normalized
  395.                     normdist = matrix_rot_z(yaw*deg2rad)*vangle
  396.                     if i>=len(list):
  397.                         new2 = new.copy()
  398.                         #
  399.                         # clock doesn't tick fast enough to give unique names
  400.                         #
  401.                         new2["targetname"] = MakeUniqueTargetname()+'.'+`i`
  402.                         undo.setspec(new,"target",new2["targetname"])
  403.                     else:
  404.                         new2 = list[i].copy()
  405.                     shift=new.origin+distance*normdist-new2.origin
  406.                     new2.translate(shift)
  407.                     if i>=len(list):
  408.                         list.append(new2)
  409.                         undo.put(new.parent, new2)
  410.                     else:
  411.                         undo.exchange(list[i],new2)
  412.                     new = new2
  413.  
  414.                 if self.src["shifttail"] and shift is not None:
  415.                    for i in range(number,len(list)):
  416.                        new = list[i].copy()
  417.                        new.translate(shift)
  418.                        undo.exchange(list[i], new)
  419.  
  420.  
  421.                 editor.ok(undo, "Move following path point(s)")
  422.                 editor.layout.explorer.uniquesel=handle.centerof
  423.  
  424.             PositionFollowingDlg(quarkx.clickform, 'positionfollowing', editor, setup, action)
  425.  
  426.  
  427.  
  428.         menulist = [qmenu.item("Insert after",  after1click)]
  429.         if (self.pathdupmaster == 0):
  430.  
  431.             # if it is not the PathDup, then it must be a PathDupCorner, and several more menuitems are available
  432.             menulist.append(qmenu.item("Insert before", before1click))
  433.             menulist.append(qmenu.item("Remove",        remove1click))
  434.             menulist.append(qmenu.item("Select main dup",   selectdup1click, "|Select main duplicator (making all path points visible)"))
  435.             menulist.append(qmenu.item("Select tail", selecttail1click, "Multi-select this & the following path points"))
  436.             menulist.append(qmenu.item("Position following", positionfollowing1click, "|Position following path points relative to this one, making new ones if necessary"))
  437.  
  438.         else:
  439.             menulist.append(qmenu.item("Retarget Path", retarget1click, "Set target/targetname specifics, following subitem order"))
  440.         menulist.append(qmenu.item("Toggle speeddraw",  speeddraw1click))
  441.  
  442.         return menulist
  443.  
  444.  
  445. class PathPointHandle(PathDuplicatorPointHandle):
  446.  
  447.     def __init__(self, origin, centerof, mainpathdup):
  448.         quarkpy.qhandles.IconHandle.__init__(self, origin, centerof)
  449.         self.pathdupmaster = 0
  450.         self.mainpathdup = mainpathdup
  451.  
  452.  
  453.     #
  454.     # called at end of drag, resets selection
  455.     #
  456.     def ok(self, editor, undo, old, new):
  457.         PathDuplicatorPointHandle.ok(self,editor,undo,old,new)
  458.         editor.layout.explorer.sellist=[self.mainpathdup.dup]
  459.  
  460.     def menu(self, editor, view):
  461.         return PathDuplicatorPointHandle.menu(self, editor, view)
  462.  
  463.         def seldup1click(m,self=self,editor=editor):
  464.             editor.layout.explorer.uniqusel=self.mainpathdup
  465.  
  466.         seldup = qmenu.item("Select duplicator",seldup1click,"select main duplicator (so that all path handles become visible)")
  467.         pointhandles = pointhandles = [seldup]
  468.         return pointhandles
  469.  
  470. class PathDuplicatorPoint(DuplicatorManager):
  471.  
  472.     Icon = (ico_dict['ico_mapdups'], 2)
  473.  
  474.     def buildimages(self, singleimage=None):
  475.         pass
  476.  
  477.     def handles(self, editor, view):
  478.         hndl = PathDuplicatorPointHandle(self.dup.origin, self.dup)
  479.         return [hndl]
  480.  
  481.     def sourcelist2(self):
  482.         myself = self.dup
  483.         list = []
  484.         if (myself.parent is not None):
  485.             for item in myself.parent.subitems:
  486.                 if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
  487.                     list.append(item)
  488.         return list
  489.  
  490. class PathDuplicator(StandardDuplicator):
  491.  
  492.     cuberadius = 3096
  493.  
  494.     def readvalues(self):
  495.         self.origin = self.dup.origin
  496.         if self.origin is None:
  497.             self.origin = quarkx.vect(0,0,0)
  498.         self.matrix = None
  499.         self.target = self.dup["target"]
  500.         try:
  501.            self.speed = int(self.dup["speeddraw"])
  502.         except:
  503.            self.speed = 0
  504.         try:
  505.            self.scaletex = int(self.dup["scaletexture"])
  506.         except:
  507.            self.scaletex = 0
  508.  
  509.     def applylinear(self, matrix, direct=0):
  510.         pass
  511.  
  512.     def do(self, item):
  513.         pass
  514.  
  515.     def sourcelist(self):
  516.         list = StandardDuplicator.sourcelist(self)
  517.         group = quarkx.newobj("group:g");
  518.         for item in list:
  519.            group.appenditem(item.copy())
  520.         evaluateDuplicators(group)
  521.         return group
  522.  
  523.     def sourcelist2(self):
  524.         myself = self.dup
  525.         list = []
  526.         if (myself.parent is not None):
  527.             for item in myself.parent.subitems:
  528.                 if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
  529.                     list.append(item)
  530.         return list
  531.  
  532.     def buildimages(self, singleimage=None):
  533.         try:
  534.             self.readvalues()
  535.         except:
  536.             print "Note: Invalid Duplicator Specific/Args."
  537.             return
  538.  
  539.         pathlist = plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2())
  540.         #pathlist.insert(0, self.dup)
  541.  
  542.         templategroup = self.sourcelist()
  543.         templatebbox = quarkx.boundingboxof([templategroup])
  544.         templatesize = templatebbox[1] - templatebbox[0]
  545.  
  546.         # If SPEEDDRAW specific is set to one, we'll only use a single cube to make the path.
  547.         if self.speed == 1:
  548.            del templategroup
  549.            templategroup = quarkx.newobj("group:g")
  550.            templategroup.appenditem(plugins.deckerutils.NewXYZCube(templatesize.x, templatesize.y, templatesize.z, quarkx.setupsubset()["DefaultTexture"]))
  551.  
  552.         if (singleimage is None and self.speed != 1):
  553.            viewabletemplategroup = templategroup.copy()
  554.            viewabletemplategroup[";view"] = str(VF_IGNORETOBUILDMAP)  # Do not send this to .MAP file
  555.            newobjs = [viewabletemplategroup]
  556.         else:
  557.            newobjs = []
  558.         templatescale = min(templatesize.x, templatesize.y)/3
  559.         OriginShift = -ObjectOrigin(templategroup)
  560.         templategroup.translate(OriginShift, 0)    # Move it to (0,0,0)
  561.  
  562.         tile = templategroup.findname("Tile:g")
  563.         if tile is not None:
  564.             templategroup.removeitem(tile)
  565.  
  566.         templatefront=getNormalFaces(templategroup.findallsubitems("",":f"),
  567.                                      quarkx.vect(1,0,0))
  568.         rimvtxes=[]
  569.         for face in templatefront:
  570.             for vtxes in face.vertices:
  571.                 for vtx in vtxes:
  572.                     rimvtxes.append(vtx)
  573.  
  574. #        # -- If SCALETEXTURES is on, use the linear() operation
  575. #        if (self.scaletex != 0):
  576. #           scalematrix = quarkx.matrix((2048/templatescale,0,0), (0,1,0), (0,0,1))
  577. #        else:
  578. #           # -- User does not want to scale textures (who wants), but then we must scale the polys in another way
  579. #           flist = templategroup.findallsubitems("", ":f")
  580. #           for f in flist:
  581. #              f.translate(quarkx.vect(2048 - f.dist,0,0) * f.normal.x)
  582.  
  583.         count = len(pathlist)-1
  584.         if (singleimage is not None) and (singleimage >= count): # Speed up Dissociate images processing
  585.             return [] # Nothing more to send back!
  586.         prevaxes = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
  587.         for i in range(count):
  588.             #DECKER 2002-08-04 part-2: Moved this if-structure above the for-loop
  589.             #if (singleimage is not None): # Speed up Dissociate images processing
  590.             #   if (singleimage >= count):
  591.             #      return [] # Nothing more to send back!
  592.             #   #DECKER 2002-08-04: Removed this else, as it would cause dissociate images to produce an incorrect result.
  593.             #   #                   Found by quantum_red in QuArK forum date: 2002-07-30 subject: "Texture Alignment Problem".
  594.             #   #else:
  595.             #   #   i = singleimage
  596.             thisorigin = pathlist[i].origin
  597.             nextorigin = pathlist[i+1].origin
  598.             #print "Image#", i, "this", thisorigin, "next", nextorigin
  599.  
  600.             list = templategroup.copy()
  601.  
  602.             pathdist = nextorigin - thisorigin
  603.  
  604. #            # -- Place center between the two paths
  605. #            neworigin = pathdist*0.5 + thisorigin
  606.  
  607.             #
  608.             # place center so textures will tile from start
  609.             #
  610.             neworigin = pathdist.normalized*0.5*templatesize.z + thisorigin
  611.  
  612.             if (pathlist[i]["level"] or self.dup["level"]) and not Vertical(pathdist):
  613.                prevaxes = MakeLevelAxes(pathdist.normalized)
  614.             else:
  615.                prevaxes = NewAxes(prevaxes,pathdist.normalized)
  616.             xax, yax, zax = prevaxes
  617.             #
  618.             # N.B. when the three args are vectors they are indeed
  619.             #  input as columns.  Tuples otoh will go in as rows.
  620.             #
  621.             mat = quarkx.matrix(xax,yax,zax)
  622.             list.translate(neworigin, 0)
  623.             list.linear(neworigin, mat)
  624.             front, back = getends(list,xax)
  625.  
  626.             for face in front:
  627.                center = projectpointtoplane(thisorigin,face.normal,face.dist*face.normal,face.normal)
  628.                face.translate(thisorigin-center,0)
  629.                if i>0:
  630.                    if singleimage is not None:
  631.                        lastx = (thisorigin-pathlist[i-1].origin).normalized
  632.                        joinnorm=((xax+lastx)/2).normalized
  633.                    face.distortion(-joinnorm,thisorigin)
  634.             for face in back:
  635.                center = projectpointtoplane(thisorigin,face.normal,face.dist*face.normal,face.normal)
  636.                face.translate(nextorigin-center,0)
  637.                if i<count-1:
  638.                    nextx=(pathlist[i+2].origin-nextorigin).normalized
  639.                    joinnorm=((xax+nextx)/2).normalized
  640.                    face.distortion(joinnorm,nextorigin)
  641.  
  642.             #
  643.             # find out where flat-ended segments would end if they
  644.             #   are to just touch at the corners
  645.             #
  646.             def vtxshift(vtx,mat=mat,orig=thisorigin, shift=OriginShift):
  647.                 return mat*(vtx+shift)+orig
  648.             if (tile is not None) or self.dup["squarend"]:
  649.                 startseg=endseg=0
  650.                 for vtx in map(vtxshift,rimvtxes):
  651.                     frontpoint=projectpointtoplane(vtx,xax,thisorigin,front[0].normal)
  652.                     frontproj = (frontpoint-thisorigin)*xax
  653.                     startseg=max(startseg,frontproj)
  654.                     backpoint=projectpointtoplane(vtx,-xax,nextorigin,back[0].normal)
  655.                     backproj = -(backpoint-nextorigin)*xax
  656.                     endseg=min(endseg,backproj)
  657.  
  658.             if tile is not None:
  659.                 tileableLength=abs(pathdist)-startseg+endseg
  660.                 tileOffset = (tileableLength%templatesize.x)/2
  661.                 tileTimes=int(tileableLength/templatesize.x)
  662.                 for i in range(tileTimes):
  663.                     if i==0:
  664.                         newTile=tile.copy()
  665.                         newTile.linear(quarkx.vect(0,0,0),mat)
  666.                         newTile.translate(thisorigin+(startseg+templatesize.x*0.5+tileOffset)*xax)
  667.                     else:
  668.                         newTile=newTile.copy()
  669.                         newTile.translate(xax*templatesize.x)
  670.                     list.appenditem(newTile)
  671.             #
  672.             #  Code below creates `box' shaped sections that just touch at the
  673.             #   edges, or are set back.  Can't do it by taking vertices of actual
  674.             #   front and back faces above because these don't seem to be
  675.             #   computed yet.
  676.             #
  677.             if self.dup["squarend"]:
  678.                  setback = self.dup["setback"]
  679.                  if setback is None:
  680.                      setback=0
  681.                  else:
  682.                      setback,=setback
  683.                  if startseg:
  684.                      start=xax*(startseg+setback)
  685.                      for face in front:
  686.                          face.translate(start)
  687.                          face.distortion(-xax,thisorigin+start)
  688.                  if endseg:
  689.                      end=xax*(endseg-setback)
  690.                      for face in back:
  691.                          face.translate(end)
  692.                          face.distortion(xax,nextorigin+end)
  693.  
  694.             if (singleimage is None) or (i==singleimage):
  695.                 newobjs = newobjs + [list]
  696.             del list
  697.             if (i==singleimage): # Speed up Dissociate images processing
  698.                 break
  699.         return newobjs
  700.  
  701.  
  702.     def handles(self, editor, view):
  703.         try:
  704.             self.readvalues()
  705.         except:
  706.             print "Note: Invalid Duplicator Specific/Args."
  707.             return
  708.         def makehandle(item,self=self):
  709.             return PathPointHandle(item.origin, item, self)
  710.         pathHandles=map(makehandle,plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2()))
  711.  
  712.         return DuplicatorManager.handles(self, editor, view) + [PathDuplicatorPointHandle(self.dup.origin, self.dup, 1)]+pathHandles
  713.  
  714.  
  715.  
  716.  
  717. class InstanceDuplicator(PathDuplicator):
  718.  
  719.     def handles(self, editor, view):
  720.         h = StandardDuplicator.handles(self, editor, view)
  721.         h[0].hint = "Copies of my contents are placed at the path points\n"
  722.         if self.dup["usercenter"] is not None:
  723.             h.append(UserCenterHandle(self.dup))
  724.         return h
  725.  
  726.  
  727.     def buildimages(self, singleimage=None):
  728.  
  729.         if len(self.dup.subitems)==0:
  730.             return
  731.  
  732.  
  733.         try:
  734.             self.readvalues()
  735.         except:
  736.             print "Note: Invalid Duplicator Specific/Args."
  737.             return
  738.  
  739.         pathlist = plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2())
  740.         #pathlist.insert(0, self.dup)
  741.  
  742.  
  743.         templategroup = self.sourcelist()
  744.         templatebbox = quarkx.boundingboxof([templategroup])
  745.         templatesize = templatebbox[1] - templatebbox[0]
  746.  
  747.         newobjs = []
  748.         if (singleimage is None and self.speed != 1):
  749.            viewabletemplategroup = templategroup.copy()
  750.            viewabletemplategroup[";view"] = str(VF_IGNORETOBUILDMAP)  # Do not send this to .MAP file
  751.            newobjs = [viewabletemplategroup]
  752.         else:
  753.            newobjs = []
  754.         templatescale = min(templatesize.x, templatesize.y)/3
  755. #        templategroup.translate(-ObjectCustomOrigin(templategroup), 0)    # Move it to (0,0,0)
  756.         if self.dup["usercenter"] is not None:
  757.             templategroup.translate(-GetUserCenter(self.dup), 0)    # Move it to (0,0,0)
  758.         elif self.dup["elbow"] is not None:
  759.             templategroup.translate(-pathlist[1].origin, 0)
  760.         else:
  761.             templategroup.translate(-ObjectOrigin(templategroup), 0)    # Move it to (0,0,0)
  762.         for item in templategroup.subitems[:]:
  763.             if item.type==":d" and item["macro"]=="dup origin":
  764.                 templategroup.removeitem(item)
  765.  
  766.         count = len(pathlist)
  767.         prevaxes = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
  768.         if self.dup["elbow"] == 1:
  769.             retromat = ~(matrix_rot_u2v(prevaxes[0],(pathdist[1]-pathdist[0]).normalized))
  770.             prevaxes = retromat*prevaxes[0],retromat*prevaxes[2],retromat*prevaxes[2],
  771.  
  772.  
  773. #        debug('count '+`count`+' image '+`singleimage`)
  774.         for i in range(count):
  775.  
  776.             if (singleimage is not None): # Speed up Dissociate images processing
  777.                if (singleimage >= count):
  778.                   return [] # Nothing more to send back!
  779.                else:
  780.                   i = singleimage
  781.  
  782.             thisorigin = pathlist[i].origin
  783.  
  784.             if (self.dup["track"] or self.dup["elbow"]) and count>1:
  785.                 if i<count-1:
  786.                     nextorigin = pathlist[i+1].origin
  787.                     pathdist = nextorigin-thisorigin
  788.                     #
  789.                     # otherwise just use last pathdist
  790.                     #
  791. #                if self.dup["elbow"] and i==count-1:
  792. #                   pathdist=pathlist[count-2].origin-pathlist[count-1].origin
  793.                 if self.dup["elbow"]:
  794.                    pathdist = thisorigin-pathlist[i-1].origin
  795.                 if (pathlist[i]["level"] or self.dup["level"]) and not Vertical(pathdist):
  796.                    prevaxes = MakeLevelAxes(pathdist.normalized)
  797.                 else:
  798.                    prevaxes = NewAxes(prevaxes,pathdist.normalized)
  799.                 xax, yax, zax = prevaxes
  800.                 #
  801.                 # N.B. when the three args are vectors they are indeed
  802.                 #  input as columns.  Tuples otoh will go in as rows.
  803.                 #
  804.                 matrix = quarkx.matrix(xax,yax,zax)
  805.             else:
  806.                 matrix=quarkx.matrix('1 0 0 0 1 0 0 0 1')
  807.  
  808. #                debug("  image %d; i %d"%(singleimage, i))
  809.  
  810.             list = templategroup.subitems[0].copy()
  811.             if not (pathlist[i]["no instance"] or (self.dup["elbow"] and (i==0 or i==count-1))):
  812.                 list.translate(thisorigin)
  813.                 list.linear(thisorigin,matrix)
  814.                 if (singleimage is None) or (i==singleimage):
  815.                     newobjs = newobjs + [list]
  816.             else:
  817.                 #
  818.                 # This cruft is necessary because dissociate1click
  819.                 #  stops the image-generation process when an empty
  820.                 #  image is returned
  821.                 #
  822.                 dummy = quarkx.newobj('dummy:g')
  823.                 newobjs = newobjs + [dummy]
  824.             del list
  825.             if (i==singleimage): # Speed up Dissociate images processing
  826.                 break
  827.  
  828.         return newobjs
  829.  
  830.  
  831. def macro_instances(self):
  832.     editor=mapeditor()
  833.     if editor is None: return
  834.     dup = editor.layout.explorer.uniquesel
  835.     if dup is None: return
  836.     undo = quarkx.action()
  837.     new=dup.copy()
  838.     new.shortname = 'Elbow duplicator'
  839.     new["macro"] = 'dup instance'
  840.     new["elbow"] = "1"
  841.     for item in new.subitems[:]:
  842.         new.removeitem(item)
  843.     new.translate(quarkx.vect(64, -64, 0))
  844.     undo.put(dup.parent, new, dup)
  845.     editor.ok(undo,"add elbows")
  846.     editor.layout.explorer.uniquesel=new
  847.  
  848. quarkpy.qmacro.MACRO_instances = macro_instances
  849.  
  850.  
  851.  
  852. quarkpy.mapduplicator.DupCodes.update({
  853.   "dup path":       PathDuplicator,
  854.   "dup path_point": PathDuplicatorPoint,
  855.   "dup instance":   InstanceDuplicator,
  856. })
  857.  
  858.  
  859. # ----------- REVISION HISTORY ------------
  860. #$Log: mapdupspath.py,v $
  861. #Revision 1.47  2002/08/04 11:49:24  decker_dk
  862. #Fixed some code that would cause dissociate images to produce an incorrect result.
  863. #Found by quantum_red in QuArK-forum date: 2002-07-30 subject: "Texture Alignment Problem".
  864. #
  865. #Revision 1.46  2001/10/22 10:15:48  tiglari
  866. #live pointer hunt, revise icon loading
  867. #
  868. #Revision 1.45  2001/07/08 20:57:56  tiglari
  869. #change treatment of vertical 'level' segments
  870. #
  871. #Revision 1.44  2001/07/08 08:34:31  tiglari
  872. #reinstated elbow button for path dup; added 'level' spec for path dup and
  873. #  instance (elbow) dup, fixed orientation bugs
  874. #
  875. #Revision 1.43  2001/06/17 21:10:57  tiglari
  876. #fix button captions
  877. #
  878. #Revision 1.42  2001/06/13 22:27:19  tiglari
  879. #fix instance dup bug
  880. #
  881. #Revision 1.41  2001/06/09 01:21:17  tiglari
  882. #fix due north bug (noted by greeze)
  883. #
  884. #Revision 1.40  2001/05/12 23:04:38  tiglari
  885. #make new linear fixpoint behavior contingent on 'item center' flag
  886. #
  887. #Revision 1.39  2001/05/06 10:22:10  tiglari
  888. #remove buildLinearMatrix stuff from instance duplicator buildimages
  889. #
  890. #Revision 1.38  2001/04/08 02:43:10  tiglari
  891. #if a group inside the instance duplicator has a usercenter, then the
  892. # matrix/scale/rotate attributes of a path point will apply the transformations
  893. # to that group, around the center.  'track rotations' otoh apply w.r.t. the
  894. # usercenter attribute of the instance duplicator itself, to all of the
  895. # subitems of the instance duplicator collectively.
  896. #
  897. #Revision 1.37  2001/04/02 21:11:27  tiglari
  898. #added is None to conditional
  899. #
  900. #Revision 1.36  2001/04/01 00:09:36  tiglari
  901. #usercenter for matrices on path points for instance duplicator
  902. #
  903. #Revision 1.35  2001/03/31 10:18:16  tiglari
  904. #revise instance duplicator to use usercenter specific
  905. #
  906. #Revision 1.34  2001/03/29 09:28:55  tiglari
  907. #scale and rotate specifics for duplicators
  908. #
  909. #Revision 1.33  2001/03/28 23:21:03  tiglari
  910. #re-integrate instance dup stuff from  1.31, which somehow got dropped
  911. #
  912. #Revision 1.32  2001/03/28 12:22:49  tiglari
  913. #fix square end problem (vertex shift function wrong)
  914. #
  915. #Revision 1.25  2001/03/18 23:54:09  tiglari
  916. #experimental merge
  917. #
  918. #
  919. #Revision 1.24  2001/03/17 22:04:53  tiglari
  920. #dissociate images fix
  921. #
  922. #Revision 1.23  2001/03/12 23:10:27  tiglari
  923. #path dup adding/positioning enhancements (does the work of the
  924. # 'torus generator' suggested plugin, inter alia)
  925. #
  926. #Revision 1.22.2.2  2001/03/12 09:21:23  tiglari
  927. #retarget of path points (basically for if a duplicator is used to produce
  928. #a complex pattern such as a spiral))
  929. #
  930. #Revision 1.22.2.1  2001/03/11 22:09:48  tiglari
  931. #position/add path points with set angles
  932. #
  933. #Revision 1.22  2001/03/08 06:23:26  tiglari
  934. #menu item to select duplicator on path point handles
  935. #
  936. #Revision 1.21  2001/03/07 20:00:13  tiglari
  937. #specific for rotation suppression
  938. #
  939. #Revision 1.20  2001/03/04 06:40:13  tiglari
  940. #changed to use arbitrary axis matrix rot from maputils
  941. #
  942. #Revision 1.19  2001/02/27 07:08:02  tiglari
  943. #center tiles along path segments
  944. #
  945. #Revision 1.18  2001/02/27 05:33:07  tiglari
  946. #fixed storage problem (map object created in class definition)
  947. #
  948. #Revision 1.17  2001/02/26 03:26:40  tiglari
  949. #handle ok method fix
  950. #
  951. #Revision 1.16  2001/02/26 02:07:21  tiglari
  952. #all path point handles appear when main dup is selected
  953. #
  954. #Revision 1.15  2001/02/25 16:32:54  decker_dk
  955. #Fix for objects that are supposed to be checked for None/Nil/Null pointer.
  956. #
  957. #Revision 1.14  2001/02/23 03:41:51  tiglari
  958. #square end and setback
  959. #
  960. #Revision 1.13  2001/02/22 21:47:57  tiglari
  961. #stuff in a Tile subitem of the duplicator (top level) will now be tiled
  962. #
  963. #Revision 1.12  2001/02/22 03:37:37  tiglari
  964. #more spiffup, also code for calculating start and end of maximal nonoverlapping
  965. #flat-end path segments
  966. #
  967. #Revision 1.10  2001/02/21 06:34:22  tiglari
  968. #a bit of cleanup, some preliminaries for elbows and tiling
  969. #
  970. #Revision 1.9  2001/02/20 21:31:38  tiglari
  971. #textures tile from start of path (still messes at elbows, this probably
  972. # needs fullon elbow segments to deal with)
  973. #
  974. #Revision 1.8  2001/02/19 19:15:57  decker_dk
  975. #Insert before/after actions now places new 'handle' at more intutive position, and aligned-to-grid.
  976. #
  977. #Revision 1.7  2001/02/11 09:46:52  tiglari
  978. #evaluation of duplicators within template
  979. #
  980. #Revision 1.6  2001/02/09 10:49:07  tiglari
  981. #fixed bug with no-angle path joints
  982. #
  983. #Revision 1.5  2001/02/08 10:44:27  tiglari
  984. #Fixed problems with paths going up & down.
  985. # Replaced subtraction code with end-face movement/distortion.
  986. #
  987. #Revision 1.4  2001/02/04 11:51:45  decker_dk
  988. #Some cleanup
  989. #
  990. #Revision 1.3  2001/02/03 19:08:30  decker_dk
  991. #Changed path-handles to ':d'-macros, so functions can be performed on them. Like adding new path-handles.
  992. #
  993. #Revision 1.2  2001/01/27 18:25:29  decker_dk
  994. #Renamed 'TextureDef' -> 'DefaultTexture'
  995. #
  996. #Revision 1.1  2000/12/19 21:07:19  decker_dk
  997. #Still a buggy Path Duplicator
  998. #
  999. #2000-??-?? Using point-entities as path-handles
  1000. #1999-02-10 First public beta.
  1001. #1999-01-23 Created.
  1002.